/*
 * ==========================================================================*\
 * | $Id$
 * |*-------------------------------------------------------------------------*|
 * | Copyright (C) 2009 Virginia Tech | | This file is part of CloudSpace. | |
 * CloudSpace is free software; you can redistribute it and/or modify | it under
 * the terms of the GNU General Public License as published | by the Free
 * Software Foundation; either version 3 of the License, or | (at your option)
 * any later version. | | CloudSpace is distributed in the hope that it will be
 * useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of |
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General
 * Public License for more details. | | You should have received a copy of the
 * GNU General Public License | along with CloudSpace; if not, see
 * <http://www.gnu.org/licenses/>.
 * \*==========================================================================
 */

package cloudspace.demoitem;

import java.io.File;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.log4j.Logger;
import org.zkoss.image.AImage;
import org.zkoss.zul.A;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Image;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Textbox;

import cloudspace.config.ServletLocationFinder;

/**
 * The controller class for the demoItems.zul.  
 * The controller does a lot more than the name suggests here. 
 * It is also responsible for talking to the model, and capturing images 
 * for the demo projects that are added.
 * 
 * @author Sunil Kamalakar
 *
 */
public class DemoItemsController extends GenericForwardComposer {

	private static final long serialVersionUID = 5823920504173577650L;
	
	//The configuration constants.
	private static final String SERVLET_PATH = ServletLocationFinder.getServletResourcePath("/");
	private static final String DEFAULT_IMAGE_LOCATION = "images/demo/";
	private static final String DEFAULT_IMAGE_NAME = "DemoImage-";
	private static final String DEFAULT_IMAGE_EXTENTION = ".png";
	private static final String DEFAULT_PORT_URI = ":8443";
	//private static final String SCREENSHOT_CMD_EXEC = "java -cp .:* ScreenshotCapturer ";
	
	//The logger
	private static Logger log = Logger.getLogger(DemoItemsController.class);

	//The model instance for the controller to work with.
	DemoItemsModel model;

	//The demo item beans bound to the UI
	private List<DemoItemBean> demoItems = new ArrayList<DemoItemBean>();

	//The UI elements wired to the controller
	private Listbox viewEditListbox;	
	private Textbox addProgramURI, addTooltip;
	private Image demoImage;
	private A imageLink;

	//The constructor
	public DemoItemsController() {
		super();
		try {
			model = new DemoItemsModel();			
		}	
		catch(Exception e) {
			String errorMessage = 
					"An error occured in the creation of the model file for demo items. \n" +
							"No demo functionality will work";
			log.error(errorMessage, e);
			//displayError(errorMessage);
			
		}
	}

	@Override
	public void doAfterCompose(Component comp) throws Exception {

		super.doAfterCompose(comp);
			
		//Set the item renderer which adds display format.
		viewEditListbox.setItemRenderer(new DemoListItemRenderer(model, this));
		updateModelInView();
	}

	public void onSelect$viewEditListbox(SelectEvent event) throws Exception {

		Listitem selectedItem = viewEditListbox.getSelectedItem();
		if(selectedItem != null) {
			DemoItemBean bean = (DemoItemBean) selectedItem.getValue();
			setViewItemValue(bean);			
		}
	}

	/*
	 * This method provides the necessary binding between the view list element
	 * and the add grid below in the UI. This binding is completely controlled by
	 * the controller and there is no usage of ZK's binding facility.  
	 */
	private void setViewItemValue(DemoItemBean bean) {
		if(bean == null) {
			//Create an empty bean to fill the values.
			bean = new DemoItemBean("", "", "", false);		
		}
		
		addProgramURI.setValue(bean.getProgramURI());
		//addImageURI.setValue(bean.getImageURI());
		addTooltip.setValue(bean.getTooltip());
		
		try {
			imageLink.setHref("/" + bean.getImageURI());
			demoImage.setContent(new AImage(SERVLET_PATH + bean.getImageURI()));		
		}
		catch(Exception e) {
			//Show no image when it is not available
			imageLink.setHref("/");
			demoImage.setSrc("");			
		}	
	}

	public void onClick$refreshItemButton(Event event) {
		updateModelInView();
	}

	public void onClick$addItemButton(Event event) {
		
		addDemoItem(addProgramURI.getValue(), null, 
						addTooltip.getValue(), true);

		updateModelInView();
	}

	public void addDemoItem(String programURIStr, String imageURIStr,
			String tooltip, boolean visibility) {
		
		try {
			if(programURIStr != null && isValidURI(programURIStr, true)) {
				
				imageURIStr = createImageFile(programURIStr);
				if(imageURIStr != null && imageURIStr != "") {
					
					addItemToModel(programURIStr, imageURIStr, 
							tooltip, visibility);
				}
			}			
		}
		catch (Exception e) {
			String errorMessage = "An error occured while adding a new demo item.\n" 
					+ e.getMessage();
			log.error(errorMessage, e);
			displayError(errorMessage);
		}
	}
	
	public void onClick$updateItemButton(Event event) {
		
		Listitem selectedItem = viewEditListbox.getSelectedItem();
		DemoItemBean bean = null;
		DemoItemBean newBean = null; 
		String newProgramURI = addProgramURI.getValue();
		String newImageURI = "";
		String newTooltip = addTooltip.getValue();
		boolean newVisibility = false;
		boolean deletePreviousImage = false;

		if(selectedItem != null) {
			bean = (DemoItemBean) selectedItem.getValue();	
			newImageURI = bean.getImageURI();
			newVisibility = bean.getVisibility();
		}

		try {
			if(bean != null) {
				if(isValidURI(newProgramURI, true)) {
					if(!bean.getProgramURI().equals(newProgramURI)) {
						newImageURI = createImageFile(newProgramURI);
						deletePreviousImage = true;
					}
					newBean = new DemoItemBean(newProgramURI, newImageURI,
							newTooltip, newVisibility);
					model.updateDemoItemInFile(bean, newBean);
					updateModelInView();
					
					if(deletePreviousImage) {
						File imageFileToDelete = new File(ServletLocationFinder.getServletResourcePath("/")
								+ bean.getImageURI());
						if(imageFileToDelete != null && imageFileToDelete.exists()) {
							imageFileToDelete.delete();						
						}
					}
				}	
			}
		}
		catch(Exception e) {
			String errorMessage = "An error occured while updating the values of demo item.\n" 
									+ e.getMessage();
			log.error(errorMessage, e);
			displayError(errorMessage);
		}
	}

	public boolean isValidURI(String programURIStr, boolean enableValidityCheck) 
								throws Exception {

		boolean retVal = true;
		programURIStr = addPortToURI(programURIStr);
		
		try {
			//If the validity check is enabled we will try for 
			//checking if the URL is valid or not.
			if(enableValidityCheck) {
				doesURLExists(programURIStr);
			}
			
			URL url = new URL(programURIStr);
			@SuppressWarnings("unused")
			URI uri = new URI(url.toString());
		}		
		catch(Exception e) {
			String errorMessage = "An error occured while parsing/fetching the URL:\n " + e.getMessage();
			log.error(errorMessage, e);
			retVal = false;
			displayError(errorMessage);
		}	

		return retVal;
	}
	
	/**
	 * This method checks if the URL exists. It sends out a HTTP request for the same.
	 * @param programURIStr
	 * @return
	 * @throws Exception
	 */
	private boolean doesURLExists(String programURIStr) throws Exception {

		boolean retVal = true;
		
		// TrustManger who does not need to validate the security chain
		TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
				public java.security.cert.X509Certificate[] getAcceptedIssuers() {return null;}
				public void checkClientTrusted(X509Certificate[] certs, String authType) {}
				public void checkServerTrusted(X509Certificate[] certs, String authType) {}
			}
		};

		SSLContext sc = SSLContext.getInstance("SSL");
		sc.init(null, trustAllCerts, new java.security.SecureRandom());
		HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

		HostnameVerifier allHostsValid = new HostnameVerifier() {
			public boolean verify(String hostname, SSLSession session) {
				return true;
			}
		};

		// Install the all-trusting host verifier
		HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
		URL url = new URL(programURIStr);
		HttpURLConnection urlConn = (HttpURLConnection)url.openConnection();
		urlConn.setConnectTimeout(30000);
		urlConn.connect();

		log.debug("HTTP Status code: " + urlConn.getResponseCode() + 
								" for URL: " + programURIStr);
		
		if(HttpURLConnection.HTTP_OK != urlConn.getResponseCode()) {
			urlConn.disconnect();
			throw new Exception("HTTP status code " + 
									urlConn.getResponseCode() + " for " + programURIStr);		
		}		

		urlConn.disconnect();
		return retVal;
	}

	private String createImageFile(String programURIStr) {
		
		StringBuilder imageURI = new StringBuilder();
		
		/*String[] temp = programURIStr.split("/");
		for(int i = temp.length - 2; i < temp.length; i++) {
			imageURI.append(temp[i].replace('.','-'));
			imageURI.append("-");
		}
		*/
		
		imageURI.append(DEFAULT_IMAGE_LOCATION);
		imageURI.append(DEFAULT_IMAGE_NAME + System.currentTimeMillis());
		imageURI.append(DEFAULT_IMAGE_EXTENTION);

		//Now let us ask for the creation of the image.
		//This may use a Virtual frame buffer on a head-less server.
		try {
						
			execImageCapture(programURIStr, imageURI.toString());
		}
		catch(Exception e) {
			String errorMessage = "Not able to capture the image for file: " + imageURI.toString()
									+ "\n" + e.getMessage();
			log.error(errorMessage, e);
			imageURI = null;
			displayError(errorMessage);
		}

		if(imageURI != null) {
			return imageURI.toString();				
		}

		return "";
	}

	public void updateModelInView() {
		demoItems = loadDataFromModel();
		if(demoItems  != null) {
			ListModel list = new ListModelList(demoItems);
			viewEditListbox.setModel(list);
		}
		
		//TODO: We can have a mechanism to retrieve the previous item selected.
		/*if(list.getSize() > 0) {
			deleteGrid.setSelectedItem((Listitem)list.getElementAt(0));		
		}
		else {
			setViewItemValue(null);		
		}
		 */
		
		setViewItemValue(null);
	}

	private String addItemToModel(String programURIStr, String imageURIStr,
									String tooltip, boolean visibility) {
		String retVal = "";	
		try {
			model.addDemoItemToFile(
						new DemoItemBean(programURIStr, imageURIStr, tooltip, visibility));			
		}	
		catch(Exception e) {
			String errorMessage = "An Exception occured while adding an entry to the demo file\n"
									+ e.getMessage();
			log.error(errorMessage, e);
			displayError(errorMessage);
		}
		return retVal;
	}

	private List<DemoItemBean> loadDataFromModel() {
		List<DemoItemBean> demoItems = null;
		try {
			demoItems = model.readDemoFile();			
		}	
		catch(Exception e) {
			String errorMessage = "An Exception occured while trying to read the demo file\n"
									+ e.getMessage();
			log.error(errorMessage, e);
			displayError(errorMessage);
		}
		return demoItems;
	}

	public void execImageCapture(String programURI, String imageURI) throws Exception{
		
		//This is no longer a exec operation, we resolved the problem where firefox binary 
		//was not being accessed.
		ScreenshotCapturer.captureScreenshot(addPortToURI(programURI), 
												SERVLET_PATH + imageURI);
		/*
		System.out.println(ServletLocationFinder.getServletResourcePath("/"));
		System.out.println("PATH : " + System.getProperty("java.library.path"));
		System.out.println("CLASSPATH : " + System.getProperty("java.class.path"));
		System.out.println("CURRENT DIR: " + System.getProperty("user.dir"));
		Process p1 = Runtime.getRuntime().exec("pwd");
		p1.waitFor();
		p1.destroy();
		
		Process p = Runtime.getRuntime().exec(SCREENSHOT_CMD_EXEC + 
												addPortToURI(programURI) + " " + imageURI);
		BufferedReader bri = new BufferedReader(new InputStreamReader(p.getInputStream()));
		BufferedReader bre = new BufferedReader(new InputStreamReader(p.getErrorStream()));
		String line = "";
		
		while ((line = bri.readLine()) != null) {
			System.out.println(line);
		}
		bri.close();
		
		while ((line = bre.readLine()) != null) {
			System.out.println(line);
		}
		bre.close();
		
		//Wait for the process to complete.
		//TODO: This should timeout after a period of time and exit.
		if(p.waitFor() < 0) {
			throw new Exception("The image capture did not terminate correctly. " +
								"The image file may not be captured");
		}
		p.destroy();
		}
		catch(Exception e) {
			String errorMessage = "An Exception occured while trying to exec the image capture: "
									+ e.getMessage();
			System.out.println(errorMessage);
			e.printStackTrace();
			displayError(errorMessage);
		}
		*/
	}

	private String addPortToURI(String uri) throws Exception {
		
		String port="";
		URL url = null;
		
		try {
			url = new URL(uri);
			port = String.valueOf(url.getPort());
		}
		catch (MalformedURLException e) {
			//Do nothing
		}
		
		if(url != null && port != "" && !uri.contains(port)) 
			return uri.replaceFirst(url.getHost(), url.getHost() + DEFAULT_PORT_URI);
		else 
			return uri;
	}	

	private void displayError(String errorMessage)
	{
		try
		{
			Messagebox.show( errorMessage,
					"Error",
					Messagebox.OK,
					Messagebox.ERROR );
		}
		catch ( Exception e )
		{
			log.error("An error occurred when displaying the error ", e);
		}
	}

}

/*
 * The item renderer associated with the View Listbox.
 */
class DemoListItemRenderer implements ListitemRenderer {
	
	private DemoItemsModel model;
	private DemoItemsController controller;
	
	public DemoListItemRenderer(DemoItemsModel model, DemoItemsController controller) {
		this.model = model;
		this.controller = controller;
	}
	
	@Override
	public void render(Listitem row, Object data) throws Exception {
		
		if (data == null) 
			return;	
		
		DemoItemBean demoItem = (DemoItemBean)data;
		
		//Let us make the programURI an hyper-link using a anchor
		Listcell programCell = new Listcell();
		A programLink = new A(demoItem.getProgramURI());
		programLink.setHref(demoItem.getProgramURI());
		programLink.setTarget("_blank");
		programCell.appendChild(programLink);
		
		row.appendChild(programCell);
		row.appendChild(new Listcell(demoItem.getTooltip()));

		Listcell visibilityCell = new Listcell();
		Checkbox visibilityCheckbox = new Checkbox();
		//visibilityCheckbox.setDisabled(true);
		visibilityCheckbox.setChecked(demoItem.getVisibility());		
		visibilityCheckbox.setParent(visibilityCell);
		
		visibilityCheckbox.addEventListener(Events.ON_CHECK, new EventListener() {
			public void onEvent(Event event) throws Exception { 
				Checkbox visibilityCheckbox = (Checkbox)event.getTarget();
				Listitem item = (Listitem)event.getTarget().getParent().getParent();
				boolean newVisibilityValue = visibilityCheckbox.isChecked();
				if(item != null) {
					DemoItemBean bean = (DemoItemBean)item.getValue();
					if(bean.getVisibility() != newVisibilityValue) {
						DemoItemBean newBean = new DemoItemBean(bean.getProgramURI(), 
												bean.getImageURI(),bean.getTooltip(), 
												newVisibilityValue);
						model.updateDemoItemInFile(bean, newBean);
						controller.updateModelInView();
					}
				}
			}
		});
		
		row.appendChild(visibilityCell);

		//Set the bean to the row now.
		row.setValue(demoItem);

		Listcell operationsCell = new Listcell();
		//Create the delete button and add the event listener.
		
		Button button = new Button(null, "/images/icons/Delete-Icon.png");
		button.setParent(operationsCell);

		button.addEventListener(Events.ON_CLICK, new EventListener() {
			public void onEvent(Event event) throws Exception {
				Listitem item = (Listitem)event.getTarget().getParent().getParent();
				String programURI = "";
				if(item != null && item.getValue() != null) {	
					DemoItemBean bean = (DemoItemBean)item.getValue();
					programURI = bean.getProgramURI();
					if(programURI != null && programURI != "" ) {
						
						model.removeDemoItemFromFile(bean);
						
						File imageFileToDelete = new File(ServletLocationFinder.getServletResourcePath("/")
								+ bean.getImageURI());
						if(imageFileToDelete != null && imageFileToDelete.exists()) {
							imageFileToDelete.delete();						
						}
						
						controller.updateModelInView();						
					}
				}
			}
		});

		Button imageCapture = new Button(null, "/images/icons/Image-Capture.png");
		imageCapture.setParent(operationsCell);
		imageCapture.addEventListener(Events.ON_CLICK, new EventListener() {
			public void onEvent(Event event) throws Exception {
				Listitem item = (Listitem)event.getTarget().getParent().getParent();
				String programURI = "";
				String imageURI = "";
				if(item != null && item.getValue() != null) {
					programURI = ((DemoItemBean)item.getValue()).getProgramURI();
					imageURI = ((DemoItemBean)item.getValue()).getImageURI();
					if(programURI != null && programURI != "" ) {
						
						if(controller.isValidURI(programURI, true)) {
							controller.execImageCapture(programURI, imageURI);			
							controller.updateModelInView();												
						}
					}
				}
			}
		});
		
		row.appendChild(operationsCell);
	}		
}

